/***************************************************************
 * Name:      SetupCreatorMain.cpp
 * Purpose:   Code for Application Frame
 * Author:    Jeremie Fouche (jeremie.fouche@gmail.com)
 * Created:   2007-05-05
 * Copyright: Jeremie Fouche ()
 * License:
 **************************************************************/

#ifdef WX_PRECOMP
#include "wx_pch.h"
#endif

#ifdef __BORLANDC__
#pragma hdrstop
#endif //__BORLANDC__

#include "setupframe.h"
#include "sc/interface.h"

#include "boost/foreach.hpp"

#include "utils/path_util.hpp"
#include "utils/traceur.h"

#define foreach BOOST_FOREACH


/// EVENT TABLE
BEGIN_EVENT_TABLE(SetupFrame, SetupFrameGui)
   EVT_MENU_RANGE(ID_MENU_ADD_BEGIN, ID_MENU_ADD_END, SetupFrame::OnContextMenuAddChild)
END_EVENT_TABLE()

/**
* SetupFrame constructor
*
* @param parent parent window (NULL)
*/
SetupFrame::SetupFrame(wxWindow* parent)
: SetupFrameGui(parent)
{
    SetIcon(wxICON(A));
    m_pgmAttributes->AddPage();
}

/**
* Called by the framework when the user need to open a context
*
* @param event
*/
void SetupFrame::OnFileOpen( wxCommandEvent& event )
{
	wxFileDialog dlg(this);
	dlg.SetWildcard( wxT("Setup creator context files (*.scc)|*.scc") );
	if (dlg.ShowModal() == wxID_OK)
	{
        int error = LoadContext(dlg.GetPath());
        if (!error)
        {
            Load();
        }
        else
        {
            ::wxMessageBox(GetErrorSC(error), _T("Setup Creator"), wxOK | wxICON_ERROR);
        }
	}
}

/**
* Called by the framework when the user need to save a setup
*
* @param event
*/
void SetupFrame::OnFileSave( wxCommandEvent& event )
{
    sc::setup::Element* pElement = GetElement( m_treeSetup->GetSelection() );
    if (pElement)
    {
        SaveAttributes(*pElement);
    }

    int error = m_context.SaveSetups();
    if (error)
    {
        ::wxMessageBox(GetErrorSC(error), _T("Setup Creator"), wxOK | wxICON_ERROR);
    }
}

/**
* Called by the framework when the user need to save a setup as ...
*  This menu is available only if there is one setup in the context
*
* @param event
*/
void SetupFrame::OnFileSaveAs( wxCommandEvent& event )
{
    assert(m_context.GetSetupCount() == 1);

	wxFileDialog dlg(this);
	dlg.SetWildcard( wxT("XML files (*.xml)|*.xml|All files (*.*)|*.*") );
	if (dlg.ShowModal() == wxID_OK)
	{
	    m_context.GetSetupContext(0)->SetXmlFile(dlg.GetPath().c_str());
	    OnFileSave(event);
	}
}

/**
* Called by the framework to set the state of the [SAVE AS...]
*
* @param event
*/
void SetupFrame::OnUpdateFileSaveAs( wxUpdateUIEvent& event )
{
    event.Enable(m_context.GetSetupCount() == 1);
}

/**
* Called by the framework when the user need to quit the application
*
* @param event
*/
void SetupFrame::OnFileExit( wxCommandEvent& event )
{
    Destroy();
}

/**
* Called by the framework when we want to add a child to the
*  current tree item.
*
* @param event
*/
void SetupFrame::OnEditAddChild( wxCommandEvent& event )
{
    wxTreeItemId tidParent = m_treeSetup->GetSelection();
    sc::SetupContext* pSetupCtx = GetSetupCtx(tidParent);
    sc::setup::Element* pParent = GetElement(tidParent);
    if (pSetupCtx && pParent)
    {
        std::set<std::string> listChildren;
        pParent->GetAvailableChildren(listChildren);
        if (listChildren.size() > 0)
        {
            DlgAddChild dlg(this, listChildren);
            if (dlg.ShowModal() == wxID_OK)
            {
                int error = AddChild(*pSetupCtx, *pParent, dlg.GetChild().c_str(), tidParent);
                if (error)
                {
                    ::wxMessageBox(GetErrorSC(error), _T("Setup Creator"), wxOK | wxICON_ERROR);
                }
            }
        }
    }
}

/**
* Called by the framework to tell if the AddChild command is available
*
* @param event
*/
void SetupFrame::OnUpdateEditAddChild( wxUpdateUIEvent& event )
{
    bool bEnable = false;

	const sc::setup::Element* pElement = GetElement( m_treeSetup->GetSelection() );
	if (pElement)
	{
	    // Does pElement have children
	    bEnable = (pElement->GetConfig()->second.GetChildren().size() > 0);
	}
	event.Enable(bEnable);
}

/**
* Called by the framework when the user selected the [Delete child]
*
* @param event
*/
void SetupFrame::OnEditDeleteChild( wxCommandEvent& event )
{
    wxTreeItemId tidElement = m_treeSetup->GetSelection();
	sc::SetupContext* pSetupCtx  = GetSetupCtx(tidElement);
	sc::setup::Element* pElement = GetElement(tidElement);
	if (pSetupCtx && pElement)
	{
		int error = DeleteElement(*pSetupCtx, pElement, tidElement);
		if (error)
		{
            ::wxMessageBox(GetErrorSC(error), _T("Setup Creator"), wxOK | wxICON_ERROR);
		}
	}
}

/**
* Called by the framework to set the state of the [Delete child]
*
* @param event
*/
void SetupFrame::OnUpdateEditDeleteChild( wxUpdateUIEvent& event )
{
/**	bool bEnable = false;

    const sc::Setup::Element* pcElement = GetElement( m_treeSetup->GetSelection() );
    if (pcElement)
    {
        // Can't remove UNIQUE Element
		if ((*(pcElement->GetConfig())).second.type != sc::Config::Element::UNIQUE)
		{
			bEnable = true;
		}
	}
	event.Enable(bEnable);*/
}

/**
* Called by the framework when the user right click on an item.
*   It show a context menu with available children that can be added, and
*   a delete menu item if possible
*
* @param event
*/
void SetupFrame::OnTreeSetupContextMenu( wxTreeEvent& event )
{
    wxTreeItemId tidElement = m_treeSetup->GetSelection();
    const sc::setup::Element* pcElement = GetElement(tidElement);
    if (pcElement)
    {
        wxMenu popup;

        // Add the available children
        std::set<std::string> listChildren;
        pcElement->GetAvailableChildren(listChildren);
        int id = ID_MENU_ADD_BEGIN;
        foreach (const std::string& sChild, listChildren)
        {
            wxString sAdd;
            sAdd.Printf(_T("Add %s"), sChild.c_str());
            popup.Append(id, sAdd);
            id++;
        }

        // Can we delete the current item ?
	    if (pcElement->GetConfigType() != sc::cfg::Element::UNIQUE)
	    {
	        popup.AppendSeparator();
	        popup.Append(ID_EDIT_DELCHILD, _T("Delete"));
	    }

        // Popup the menu if there are available actions
	    if (popup.GetMenuItemCount() != 0)
	    {
	        PopupMenu(&popup);
	    }
    }
}

/**
* Called by the framework when clicking on a context menu to add an element
*
* @param event
*/
void SetupFrame::OnContextMenuAddChild( wxCommandEvent& event )
{
    wxTreeItemId tidParent = m_treeSetup->GetSelection();
    sc::SetupContext* pSetupCtx = GetSetupCtx(tidParent);
    sc::setup::Element* pParent = GetElement(tidParent);
    if (pSetupCtx && pParent)
    {
        int idx = event.GetId() - ID_MENU_ADD_BEGIN;

        // Get the available children
        std::set<std::string> listChildren;
        pParent->GetAvailableChildren(listChildren);
        foreach (const std::string& sChild, listChildren)
        {
            if (idx == 0)
            {
                // We found the child we want to add
                int error = AddChild(*pSetupCtx, *pParent, sChild, tidParent);
                if (error)
                {
                    ::wxMessageBox(GetErrorSC(error), _T("Setup Creator"), wxOK | wxICON_ERROR);
                }
                break;
            }
            idx--;
        }
    }
}

/**
* Called by the framework when a tree selection is changing.
*  We save the attributes before changing the right panel
*
* @param event
*/
void SetupFrame::OnTreeSetupSelChanging( wxTreeEvent& event )
{
    wxTreeItemId tidElement = m_treeSetup->GetSelection();
	sc::SetupContext* pSetupCtx = GetSetupCtx(tidElement);
	sc::setup::Element* pElement = GetElement(tidElement);
	if (pSetupCtx && pElement)
	{
        SaveAttributes(*pElement);

        // Change the tree label if necessary
	    m_treeSetup->SetItemText(tidElement, pSetupCtx->GetElementLabel(*pElement).c_str());

	    // Sort the items
	    m_treeSetup->SortChildren(m_treeSetup->GetItemParent(tidElement));
	}
}

/**
* Called by the framework when a tree selection has changed.
*  Set all the controls of the right panel
*
* @param event
*/
void SetupFrame::OnTreeSetupSelChanged( wxTreeEvent& event )
{
    RemoveAllControls();

    // Show the atrributes of the new selected Element
    wxTreeItemId tidElement = m_treeSetup->GetSelection();
	sc::SetupContext* pSetupCtx  = GetSetupCtx(tidElement);
	sc::setup::Element* pElement = GetElement(tidElement);
	if (pElement)
	{
        SetControls(*pSetupCtx, *pElement);
	}

	m_pgmAttributes->Refresh();
}

/**
* Called by the framework while right clicking on a tree item.
*   This must select the item.
*
* @param event
*/
void SetupFrame::OnTreeSetupRightClick( wxTreeEvent& event )
{
    m_treeSetup->SelectItem(event.GetItem());
}

/**
* Load a context
*
* @param sFilename
*
* @return the context loading error
*/
int SetupFrame::LoadContext(const wxString& sFilename)
{
Traceur::Printf("SetupFrame::LoadContext(%s)", sFilename.c_str());
    int error = m_context.Load(sFilename.c_str());
    if (!error)
    {
        Load();
    }
    else
    {
        ::wxMessageBox(GetErrorSC(error), _T("Setup Creator"), wxOK | wxICON_ERROR);
    }
    return error;
}

/**
* Load all the information in the frame :
*  - Load the icons
*  - Fill the tree
*/
void SetupFrame::Load(void)
{
    m_treeSetup->DeleteAllItems();

    // Set the title
    SetTitle( m_context.GetTitle().c_str() );

    // Load the icons
    static wxImageList imgList(16,16);
    imgList.RemoveAll();
    m_mapIcons.clear();

    for (size_t idx = 0; idx < m_context.GetSetupCount(); ++idx)
    {
        const sc::SetupContext* pcSetupCtx = m_context.GetSetupContext(idx);
        foreach (const sc::SetupContext::TMmiElements::value_type& rcMmiVal, pcSetupCtx->GetMmiElements())
        {
            const std::string& sIcon = rcMmiVal.second.sIcon;
            if (!sIcon.empty())
            {
Traceur::Printf("SetupFrame::Load - icon [%s]", sIcon.c_str());

                wxIcon icon(rcMmiVal.second.sIcon.c_str(), wxBITMAP_TYPE_ICO);
                if (icon.IsOk())
                {
                    int idx = imgList.Add(icon);
                    if (idx != -1)
                    {
                        m_mapIcons[rcMmiVal.first] = idx;
                    }
                }
            }
        }
    }
    m_treeSetup->SetImageList(&imgList);

    wxTreeItemId idRoot = m_treeSetup->AddRoot(_T("SETUP"));
    assert(idRoot.IsOk());
    for (size_t idx = 0; idx < m_context.GetSetupCount(); ++idx)
    {
        const sc::SetupContext* pcSetupCtx = m_context.GetSetupContext(idx);
        // Fill the tree recursively
        AddTreeChild(idRoot, pcSetupCtx, pcSetupCtx->GetSetup().Get());
    }
}

/**
* Called by the framework we the user wants to see the about dialog
*
* @param event
*/
void SetupFrame::OnHelpAbout( wxCommandEvent& event )
{
    DlgAbout dlg(this);
    dlg.ShowModal();
}

/**
* Add a child and it's children to the tree.
*
* @param hParent    handle to the tree item parent
* @param pcElement  element to be added to the tree
*
* @return handle to the tree item created
*/
wxTreeItemId SetupFrame::AddTreeChild(const wxTreeItemId& hParent, const sc::SetupContext* pcSetupCtx, const sc::setup::Element* pcElement)
{
    assert(hParent.IsOk() && pcSetupCtx && pcElement);

    // Get the label
    std::string sLabel = pcSetupCtx->GetElementLabel(*pcElement);

    // Get the icon ID
    TIcons::const_iterator iterIcon = m_mapIcons.find(pcElement->GetName());
    int idIcon = (iterIcon == m_mapIcons.end()) ? -1 : iterIcon->second;

Traceur::Printf("SetupFrame::AddTreeChild(%s) -> idIcon=%d", pcElement->GetName().c_str(), idIcon);

    // Add the child to the tree
    wxTreeItemId newItem = m_treeSetup->AppendItem(hParent, sLabel.c_str(), idIcon);
    assert(newItem.IsOk());

    // Assign the Element to the new tree item
	ElementTID* ptid = new ElementTID( const_cast<sc::SetupContext*>(pcSetupCtx)
	                                 , const_cast<sc::setup::Element*>(pcElement)
	                                 );
	m_treeSetup->SetItemData(newItem, ptid);

	// Add the existing children
	foreach (const sc::setup::Element& rcChild, pcElement->GetChildren())
	{
		AddTreeChild(newItem, pcSetupCtx, &rcChild);
	}

	// Sort the items
    m_treeSetup->SortChildren(newItem);

	return newItem;
}

/**
* Get the SetupContext corresponding to a tree item
*
* @return the SetupContext, or NULL
*/
sc::SetupContext* SetupFrame::GetSetupCtx(const wxTreeItemId& tid) const
{
    sc::SetupContext* pSetupCtx = NULL;
    if (tid.IsOk())
    {
        ElementTID* pElemTID = dynamic_cast<ElementTID*>(m_treeSetup->GetItemData(tid));
        if (pElemTID)
        {
            pSetupCtx = pElemTID->m_pSetupCtx;
        }
    }
    return pSetupCtx;
}

/**
* Get the Setup Element corresponding to a tree item
*
* @return the Element, or NULL
*/
sc::setup::Element* SetupFrame::GetElement(const wxTreeItemId& tid) const
{
    sc::setup::Element* pElement = NULL;
    if (tid.IsOk())
    {
        ElementTID* pElemTID = dynamic_cast<ElementTID*>(m_treeSetup->GetItemData(tid));
        if (pElemTID)
        {
            pElement = pElemTID->m_pElement;
        }
    }
    return pElement;
}

/**
* Remove all the right panel controls, representing the Element attributes
*/
void SetupFrame::RemoveAllControls(void)
{
    m_pgmAttributes->ClearPage(0);
}

/**
* Set all the controls in the right panel, according to the attributes
*  of the Element.
*
* @param rSetupCtx Setup context
* @param rElement  Element of which we are creating the attributes
*/
void SetupFrame::SetControls(sc::SetupContext& rSetupCtx, sc::setup::Element& rElement)
{
    m_pgmAttributes->AppendCategory(_T("Attributes"));

	// Create all the controls for the attributes of the current Element
	sc::setup::TAttributes::iterator iter;
	for (iter = rElement.AttributesBegin(); iter != rElement.AttributesEnd(); iter++)
	{
		// Get the attribute
		sc::setup::Attribute& rAttrib = *iter;

		wxString sLabel = rSetupCtx.GetAttributeLabel(rAttrib);
		if (rAttrib.m_rcConf.GetType() == sc::cfg::Attribute::CHOICE)
		{
			wxArrayString choices;
			foreach (const std::string& sChoice, rAttrib.m_rcConf.GetChoices())
			{
				choices.Add(sChoice.c_str());
			}
            wxPGId pgId = m_pgmAttributes->Append( wxEnumProperty( sLabel
                                                                 , rAttrib.m_rcConf.GetName().c_str()
                                                                 , choices
                                                                 , choices.Index(rAttrib.m_sValue.c_str())
                                                                 )
                                                 );
            m_pgmAttributes->SetPropertyHelpString(pgId, rSetupCtx.GetAttributeHelp(rAttrib).c_str());
		}
		else
		{
            wxPGId pgId = m_pgmAttributes->Append( wxStringProperty( sLabel
                                                                   , rAttrib.m_rcConf.GetName().c_str()
                                                                   , rAttrib.m_sValue.c_str()
                                                                   )
                                                 );
            m_pgmAttributes->SetPropertyValidator(pgId,  AttributeValidator(rAttrib.m_rcConf));
            m_pgmAttributes->SetPropertyHelpString(pgId, rSetupCtx.GetAttributeHelp(rAttrib).c_str());
		}
	}
}

/**
* Save all the attributes of the current element
*/
void SetupFrame::SaveAttributes(sc::setup::Element& rElement)
{
Traceur::Printf("SetupFrame::SaveAttributes(%s)", rElement.GetName().c_str());

    sc::setup::TAttributes::iterator iterAttrib;
	for (iterAttrib = rElement.AttributesBegin(); iterAttrib != rElement.AttributesEnd(); ++iterAttrib)
	{
	    wxString sValue = m_pgmAttributes->GetPropertyValueAsString( iterAttrib->m_rcConf.GetName().c_str() );

Traceur::Printf("  * Attribute[%s] = %s", iterAttrib->m_rcConf.GetName().c_str(), sValue.c_str());

		iterAttrib->m_sValue = sValue.c_str();
	}
}

/**
* Add a child to an element of the setup
*
* @param rParent  Element parent
* @param sChild   name of the child to be added
* @param hParent  handle of the parent tree item
*
* @return add error
*/
int SetupFrame::AddChild(sc::SetupContext& rSetupCtx, sc::setup::Element& rParent, const std::string& sChild, const wxTreeItemId& hParent)
{
	sc::setup::Element child(sChild, "", rSetupCtx.GetSetup().GetConfig().GetElements().find(sChild));
	sc::setup::TElements::iterator iterChild;
	int error = rParent.AddChild(child, iterChild);
	if (!error)
	{
        wxTreeItemId hChild = AddTreeChild(hParent, &rSetupCtx, &(*iterChild));
        m_treeSetup->SortChildren(hParent);
        m_treeSetup->SelectItem(hChild);
	}
	return error;
}

/**
* Delete an element of the setup
*
* @param rSetupCtx  SetupContext of the current Element
* @param pElement  Element to delete
* @param hElement  handle of the element tree item to be removed
*
* @return delete error
*/
int SetupFrame::DeleteElement(sc::SetupContext& rSetupCtx, sc::setup::Element* pElement, const wxTreeItemId& hElement)
{
	int error =rSetupCtx.GetSetup().DeleteElement(pElement);
	if (!error)
	{
        // First, we must save the parent item to select it after delete
        wxTreeItemId tidParent = m_treeSetup->GetItemParent(hElement);
        m_treeSetup->Delete(hElement);
        m_treeSetup->SelectItem(tidParent);
	}
	return error;
}

/**
* Return the string corresponding to the error code
*
* @param error
*
* @return error string
*/
wxString SetupFrame::GetErrorSC(int error) const
{
    wxString sError;
    switch (error)
    {
    case sc::SCE_RNG_NOT_LOADED :
        sError = _T("Config file (RNG) not loaded");
        break;

    case sc::SCE_SHEMA_INVALID :
        sError = _T("Schema invalide");
        break;

    case sc::SCE_XPATH_CTX_INVALID :
        sError = _T("Context XPath invalid");
        break;

    case sc::SCE_XPATH_INVALID_EXPRESSION :
        sError = _T("Expression XPath invalid");
        break;

    case sc::SCE_XPATH_NO_RESULT :
        sError = _T("XPath n'a renvoyé aucun résultat");
        break;

	case sc::SCE_CHILD_INVALID :
        sError = _T("L'enfant n'est pas valide");
        break;

	case sc::SCE_ELEMENT_DOESNT_EXIST :
        sError = _T("L'element n'existe pas");
        break;

	case sc::SCE_CANT_SAVE_FILE :
        sError = _T("Impossible de sauvegarder le fichier");
        break;

    case sc::SCE_CANT_OPEN_FILE:
        sError = _T("Impossible d'ouvrir le fichier");
        break;

	case sc::SCE_SCC_NOT_LOADED :
        sError = _T("Le context n'est pas chargé");
        break;

    case sc::SCE_UNKNOWN :
    default :
        sError.Printf(_T("Erreur inconnue : %d"), error);
        break;
    }
    return sError;
}
